package handlers

import (
	"context"
	"encoding/json"
	"io"
	"net/http"
	"smartposts/internal/ai"
	"smartposts/internal/global"
	"smartposts/internal/models"
	"smartposts/internal/mq"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"
)

type CreatePostInput struct {
	Title   string `json:"title" binding:"required"`
	Content string `json:"content" binding:"required"`
}

func CreatePost(c *gin.Context) {
	var input CreatePostInput
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	username, _ := c.Get("username")

	var user models.User
	if result := global.Db.Where("username = ?", username).First(&user); result.Error != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}

	post := models.Post{
		Title:   input.Title,
		Content: input.Content,
		UserID:  user.ID,
	}

	if result := global.Db.Create(&post); result.Error != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": result.Error.Error()})
		return
	}

	// Publish a message to the worker to update Redis
	body, err := json.Marshal(post)
	if err != nil {
		// Log the error, but don't fail the request since the post was created.
		// The cache can be rebuilt later.
		// log.Printf("Failed to marshal post ID for queue: %v", err)
		c.JSON(http.StatusCreated, post)
		return
	}

	if err := mq.MQ.Publish("rank.update", body); err != nil {
		// Log the error, but don't fail the request.
		// log.Printf("Failed to publish post creation message: %v", err)
	}

	c.JSON(http.StatusCreated, post)
}

func GetPost(c *gin.Context) {
	var post models.Post
	id := c.Param("id")

	if result := global.Db.First(&post, id); result.Error != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Post not found"})
		return
	}

	c.JSON(http.StatusOK, post)
}

func GetPosts(c *gin.Context) {
	var posts []models.Post

	if result := global.Db.Find(&posts); result.Error != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": result.Error.Error()})
		return
	}

	c.JSON(http.StatusOK, posts)
}

type UpdatePostInput struct {
	Title   string `json:"title"`
	Content string `json:"content"`
}

func UpdatePost(c *gin.Context) {
	ctx := context.Background()
	var post models.Post
	id := c.Param("id")

	if result := global.Db.First(&post, id); result.Error != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Post not found"})
		return
	}

	username, _ := c.Get("username")

	var user models.User
	if result := global.Db.Where("username = ?", username).First(&user); result.Error != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}

	if post.UserID != user.ID {
		c.JSON(http.StatusForbidden, gin.H{"error": "You are not authorized to update this post"})
		return
	}

	var input UpdatePostInput
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	if input.Title != "" {
		post.Title = input.Title
	}

	if input.Content != "" {
		post.Content = input.Content
	}

	if result := global.Db.Save(&post); result.Error != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": result.Error.Error()})
		return
	}

	// Invalidate cache
	postKey := "post:" + id
	if err := global.RedisDB.Del(ctx, postKey).Err(); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, post)
}

func DeletePost(c *gin.Context) {
	ctx := context.Background()
	var post models.Post
	id := c.Param("id")

	if result := global.Db.First(&post, id); result.Error != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Post not found"})
		return
	}

	username, _ := c.Get("username")

	var user models.User
	if result := global.Db.Where("username = ?", username).First(&user); result.Error != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}

	if post.UserID != user.ID {
		c.JSON(http.StatusForbidden, gin.H{"error": "You are not authorized to delete this post"})
		return
	}

	if result := global.Db.Delete(&post); result.Error != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": result.Error.Error()})
		return
	}

	// Invalidate cache
	postKey := "post:" + id
	if err := global.RedisDB.Del(ctx, postKey).Err(); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	// Remove from hot posts
	if err := global.RedisDB.ZRem(ctx, "hot_posts", id).Err(); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "Post deleted successfully"})
}

type HotPostInfo struct {
	ID        uint      `json:"ID"`
	Title     string    `json:"Title"`
	Author    string    `json:"Author"`
	CreatedAt time.Time `json:"CreatedAt"`
}

func GetHotPostsBasicInfo(c *gin.Context) {
	ctx := context.Background()
	hotIds, err := global.RedisDB.ZRevRange(ctx, "hot_posts", 0, 9).Result()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get hot posts"})
		return
	}

	hotPosts := make([]HotPostInfo, 0, len(hotIds))

	for _, idStr := range hotIds {
		key := "post:" + idStr
		postMap, err := global.RedisDB.HGetAll(ctx, key).Result()

		var post HotPostInfo
		useCache := false

		if err == nil && len(postMap) > 0 && postMap["Author"] != "" {
			id, errId := strconv.ParseUint(postMap["ID"], 10, 64)
			createdAt, errTime := time.Parse(time.RFC3339, postMap["CreatedAt"])

			if errId == nil && errTime == nil {
				post.ID = uint(id)
				post.Title = postMap["Title"]
				post.Author = postMap["Author"]
				post.CreatedAt = createdAt
				useCache = true
			}
		}

		if useCache {
			hotPosts = append(hotPosts, post)
		} else {
			var fullPost models.Post
			id, _ := strconv.ParseUint(idStr, 10, 64)
			if res := global.Db.First(&fullPost, uint(id)); res.Error == nil {
				var user models.User
				if userRes := global.Db.First(&user, fullPost.UserID); userRes.Error == nil {
					post.ID = fullPost.ID
					post.Title = fullPost.Title
					post.Author = user.Username
					post.CreatedAt = fullPost.CreatedAt
					hotPosts = append(hotPosts, post)

					newPostMap := map[string]interface{}{
						"ID":        idStr,
						"Title":     post.Title,
						"Author":    post.Author,
						"CreatedAt": post.CreatedAt.Format(time.RFC3339),
					}
					if err := global.RedisDB.HSet(ctx, key, newPostMap).Err(); err != nil {
						// Log error but don't fail the request
						c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to cache to redis"})
						return
					}
				} else {
					// user not found, maybe log it but don't fail the whole request
					c.JSON(http.StatusInternalServerError, gin.H{"error": "User not found"})
					return
				}
			} else {
				// post not found, maybe log it but don't fail the whole request
				c.JSON(http.StatusInternalServerError, gin.H{"error": "Post not found"})
				return
			}
		}
	}
	c.JSON(http.StatusOK, hotPosts)
}

type AskInput struct {
	Query string `json:"query" binding:"required"`
}

func AskPost(c *gin.Context) {
	var post models.Post
	id := c.Param("id")

	if result := global.Db.First(&post, id); result.Error != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Post not found"})
		return
	}

	var input AskInput
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	aiChan, err := ai.Ask(c.Request.Context(), input.Query, post.Content, post.Title)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.Stream(func(w io.Writer) bool {
		msg, ok := <-aiChan
		if !ok {
			return false
		}
		c.SSEvent("message", msg)
		return true
	})
}

func AskPostStream(c *gin.Context) {
	var post models.Post
	id := c.Param("id")
	query := c.Query("query")

	if result := global.Db.First(&post, id); result.Error != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Post not found"})
		return
	}

	aiChan, err := ai.Ask(c.Request.Context(), query, post.Content, post.Title)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.Stream(func(w io.Writer) bool {
		msg, ok := <-aiChan
		if !ok {
			return false
		}
		c.SSEvent("message", msg)
		return true
	})
}
